Skip to content

feat: surface GraphQL extensions.code on returned errors#39

Merged
lakhansamani merged 2 commits into
mainfrom
feat/error-extensions-code
Jul 11, 2026
Merged

feat: surface GraphQL extensions.code on returned errors#39
lakhansamani merged 2 commits into
mainfrom
feat/error-extensions-code

Conversation

@lakhansamani

Copy link
Copy Markdown
Contributor

Summary

  • toErrorList() in both src/index.ts and src/admin.ts discarded everything from a GraphQL error but the message string, so callers couldn't distinguish error kinds (e.g. the new TOTP lockout in the main authorizer server) without matching on message text.
  • Added an additive, optional code field (AuthorizerSDKError) populated from the server's extensions.code when present. Non-breaking — anything reading only .message is unaffected.

Context

The authorizer server now sets extensions.code on typed service errors over GraphQL (e.g. TOO_MANY_REQUESTS for the TOTP lockout — authorizerdev/authorizer#670). This SDK change is required before authorizer-react can key its lockout UI off a stable code instead of the raw message text.

Test plan

  • New unit tests (__test__/errorCode.test.ts) with a mocked fetch, asserting .code is populated when the server sends extensions.code and left undefined otherwise
  • Full suite green: npm test (61 tests, 5 suites, including the existing testcontainers-based integration tests)
  • tsc --noEmit clean

toErrorList() discarded everything but the message string from
GraphQL errors, so callers had no way to distinguish error kinds
(e.g. the TOTP lockout from a plain invalid-otp) without matching on
message text. Attach an additive, optional `.code` from the server's
extensions.code (authorizer's GraphQL layer now sets this for typed
service errors) onto the returned Error objects via a new
AuthorizerSDKError type. Non-breaking: existing code reading only
.message is unaffected.
Second-pass review found two things worth tightening:

1. "Purely additive" wasn't quite true: err.code = code adds an
   enumerable own property to a native Error where none existed
   before, so Object.keys(error) and JSON.stringify(error) change
   output on every error that carries a code. Switched to
   Object.defineProperty with enumerable: false so the field is
   readable but genuinely invisible to anything that enumerates or
   serializes the error object.

2. The array-branch conversion logic (message + extensions.code
   extraction) was duplicated byte-for-byte between index.ts and
   admin.ts's toErrorList - exactly the kind of copy that drifts when
   the next extensions.* field gets added to only one of them.
   Extracted to a shared toSDKError in a new src/errors.ts, imported
   by both.

Added a test asserting Object.keys()/JSON.stringify() are unaffected
by a present code.
@lakhansamani

Copy link
Copy Markdown
Contributor Author

Addressed both review findings: error.code is now attached via Object.defineProperty with enumerable: false, so it's genuinely invisible to Object.keys()/JSON.stringify() (previously it changed that output, so "purely additive" wasn't quite true). Also extracted the duplicated array-branch conversion logic (shared by index.ts and admin.ts) into a single toSDKError helper in a new src/errors.ts, closing the gap where admin.ts's copy had zero test coverage of its own. Full suite green (62 tests).

@lakhansamani lakhansamani merged commit 4ec9e5b into main Jul 11, 2026
2 checks passed
@lakhansamani lakhansamani deleted the feat/error-extensions-code branch July 11, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant